package pl.th.permission;

import static pl.th.permission.PermissionCompat.PERMISSION_CREATE_NOTIFICATION;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;

public abstract class ProxyFragment extends Fragment {

    final static String TAG = "proxyFragment";

    private boolean invoked;

    private Request request;

    private int mScreenOrientation;

    private Callback mCallback;

    private Deque<IProcessor> mPermissionProcessors;

    private Dialog mCurrentDialog;

    int osVersion;
    int targetVersion;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.e(TAG, this.toString() + ": onCreate");
    }

    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        lockScreenOrientation();
    }

    @SuppressLint("SourceLockedOrientationActivity")
    @SuppressWarnings("ConstantConditions")
    private void lockScreenOrientation() {
        mScreenOrientation = getActivity().getRequestedOrientation();
        int curOrientation = getActivity().getResources().getConfiguration().orientation;
        /*
         * while the activity`s window on Android O is translucent,
         * for example, use the theme which {<item name="android:windowIsTranslucent">true</item>}
         * then Setting the orientation to portrait or landscape will throw exception
         */
        try {
            if (curOrientation == Configuration.ORIENTATION_PORTRAIT) {
                getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
            } else if (curOrientation == Configuration.ORIENTATION_LANDSCAPE) {
                getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
            }
        } catch (IllegalStateException e) {
            // Android O : java.lang.IllegalStateException: Only fullscreen activities can request orientation
            if (BuildConfig.DEBUG) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        if (request == null) {
            detachFromActivity();
            return;
        }
        if (!invoked) {
            invoked = true;
            beginRequestPermission(new ArrayList<>(Arrays.asList(request.permissions)));
        }
    }

    @SuppressWarnings("ConstantConditions")
    private void beginRequestPermission(final List<String> permissions) {
        osVersion = Build.VERSION.SDK_INT;
        targetVersion = getActivity().getApplicationInfo().targetSdkVersion;
        if ((mCurrentDialog = createReminderDialog(permissions)) != null) {
            mCurrentDialog.show();
        } else {
            requestPermission(permissions);
        }
    }

    /**
     * call it to start first traversal
     */
    protected void requestPermission(List<String> permissions) {
        createProcessors(permissions);
        workOnNextProcessor();
    }

    private void continueRequestPermission(@Nullable final List<String> grantedList,
                                           @NonNull final List<String> deniedList) {
        if ((mCurrentDialog = createRationaleDialog(grantedList, deniedList)) != null) {
            mCurrentDialog.show();
        } else {
            endRequestPermission(grantedList == null ? new LinkedList<>() : grantedList, deniedList);
        }
    }

    /**
     * it was used to continue request permission after part permission
     * is denied on first traversal
     * @param permissions, which will request again
     */
    protected void requestPermissionAgain(final List<String> permissions) {
        createAllTypeProcessors(permissions);
        workOnNextProcessor();
    }

    private List<String> findEnableRequestedPermission(final List<String> permissions) {
        final List<String> enabledRequestedList = new ArrayList<>(permissions.size());
        for (String permission : permissions) {
            if (!PermissionCompat.isPermissionAlwaysDenied(getActivity(), permission)) {
                enabledRequestedList.add(permission);
            }
        }
        return enabledRequestedList;
    }

    @SuppressWarnings("ConstantConditions")
    private void detachFromActivity() {
        getActivity().getSupportFragmentManager()
                .beginTransaction()
                .remove(this)
                .commitAllowingStateLoss();
    }

    @Override
    public void onDetach() {
        super.onDetach();
        if (mCurrentDialog != null) {
            mCurrentDialog.dismiss();
        }
        unlockScreenOrientation();
    }

    @SuppressLint("SourceLockedOrientationActivity")
    @SuppressWarnings("ConstantConditions")
    private void unlockScreenOrientation() {
        getActivity().setRequestedOrientation(mScreenOrientation);
    }

    final void setRequest(Request request) {
        this.request = request;
        mPermissionProcessors = new LinkedList<>();
    }

    public void setCallback(Callback callback) {
        this.mCallback = callback;
    }

    public void startActivityForResult(Intent intent) {
        super.startActivityForResult(intent, request.requestCode);
    }

    public void requestPermissions(String[] requestPermissions) {
        if (!Utils.isPermissionEmpty(requestPermissions)) {
            requestPermissions(requestPermissions, request.requestCode);
        } else {
            workOnNextProcessor();
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        final Activity activity = getActivity();
        if (activity == null) return;
        //to keep fragment calling {onResume()} in order
        activity.runOnUiThread(this::workOnNextProcessor);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        workOnNextProcessor();
    }

    public void insertToProcessors(IProcessor processor) {
        mPermissionProcessors.offerFirst(processor);
    }

    void workOnNextProcessor() {
        // avoid Android System destroy activity and recreate new one
        if (!isAdded()) return;
        if (request == null) return;
        if (!mPermissionProcessors.isEmpty()) {
            IProcessor processor = mPermissionProcessors.poll();
            if (processor != null) {
                processor.process(this);
            }
        } else {
            onAllProcessorTraversed();
        }
    }

    /**
     * it was called when all of the permissions are requested
     */
    protected void onAllProcessorTraversed() {
        final String[] permissions = request.permissions;
        final List<String> granted = new LinkedList<>();
        final List<String> denied = new LinkedList<>();
        for (String curPermission : permissions) {
            if (PermissionCompat.isPermissionGranted(getActivity(), curPermission)) {
                granted.add(curPermission);
            } else {
                denied.add(curPermission);
            }
        }
        if (!denied.isEmpty()) {
            continueRequestPermission(granted, denied);
        } else {
            endRequestPermission(granted, denied);
        }
    }

    /**
     * it was called when all of the permissions are requested and will callback
     * it can be used for the click callback of the rational dialog`s negative button
     */
    protected void endRequestPermission(List<String> grantedList, List<String> deniedList) {
        detachFromActivity();
        if (grantedList != null && grantedList.size() > 0) {
            String[] granted = new String[grantedList.size()];
            grantedList.toArray(granted);
            mCallback.onPermissionGranted(request.requestCode, granted);
        }
        if (deniedList != null && deniedList.size() > 0) {
            String[] denied = new String[deniedList.size()];
            deniedList.toArray(denied);
            mCallback.onPermissionDenied(request.requestCode, denied);
        }
    }

    protected void createProcessors(List<String> permissions) {
        mPermissionProcessors.clear();
        createSpecialProcessors(permissions);
        if (permissions.size() > 0) {
            String[] perArr = new String[permissions.size()];
            permissions.toArray(perArr);
            mPermissionProcessors.offer(new RuntimePermsProcessor(perArr));
        }
    }

    protected void createAllTypeProcessors(List<String> permissions) {
        List<String> enableList = findEnableRequestedPermission(permissions);
        createProcessors(enableList);
        final int size = enableList.size();
        if (size > 0 && size < permissions.size()) {
            mPermissionProcessors.offer(new AppDetailProcessor());
        }
    }

    @SuppressLint("InlinedApi")
    private void createSpecialProcessors(List<String> permissions) {
        if (removeTargetPermission(permissions, PERMISSION_CREATE_NOTIFICATION)) {
            mPermissionProcessors.offer(new NotificationPerKProcessor(PERMISSION_CREATE_NOTIFICATION));
        }
        if (removeTargetPermission(permissions, Manifest.permission.MANAGE_EXTERNAL_STORAGE)) {
            mPermissionProcessors.offer(new StoragePerRProcessor(Manifest.permission.MANAGE_EXTERNAL_STORAGE));
        }
        if (removeTargetPermission(permissions, Manifest.permission.PACKAGE_USAGE_STATS)) {
            mPermissionProcessors.offer(new PUStatePerLProcessor(Manifest.permission.PACKAGE_USAGE_STATS));
        }
        if (removeTargetPermission(permissions, Manifest.permission.REQUEST_INSTALL_PACKAGES)) {
            mPermissionProcessors.offer(new InstallAppPerOProcessor(Manifest.permission.REQUEST_INSTALL_PACKAGES));
        }
        if (removeTargetPermission(permissions, Manifest.permission.SYSTEM_ALERT_WINDOW)) {
            mPermissionProcessors.offer(new SAWindowPerMProcessor(Manifest.permission.SYSTEM_ALERT_WINDOW));
        }
        if (removeTargetPermission(permissions, Manifest.permission.WRITE_SETTINGS)) {
            mPermissionProcessors.offer(new WriteSettingPerProcessor(Manifest.permission.WRITE_SETTINGS));
        }
    }

    private boolean removeTargetPermission(List<String> request, String target) {
        return request.remove(target);
    }

    /**
     * {@link DefaultProxyFragment}
     * create Reminder Dialog before first request permission
     * notify: you should call {@link #requestPermission(List)} to start request when you
     * click the  positive button,{@link android.content.DialogInterface.OnClickListener}
     * and call {@link #onAllProcessorTraversed()} when you click the negative button.
     * @return null to jump or a dialog instance to choose
     */
    protected abstract @Nullable Dialog createReminderDialog(final List<String> permissions);

    /**
     * {@link DefaultProxyFragment}
     * create Reminder Dialog before first request permission
     * notify: you should call {@link #requestPermissionAgain(List)} to start request again when you
     * click the  positive button,{@link android.content.DialogInterface.OnClickListener}
     * and call {@link #endRequestPermission(List, List)} when you click the negative button.
     * @return null to jump or a dialog instance to choose
     */
    protected abstract @Nullable Dialog createRationaleDialog(final List<String> grantedList, final List<String> deniedList);

}
